/*
 * VideoCapture.cpp
 *
 *  Created on: 2014-4-9
 *      Author: root
 */

#include "VideoCapture.hh"

//创建对象函数
VideoCapture* VideoCapture::createNew(int Width ,int Height)
{
	if((0 < Width) && (0 < Height))
	{
		return new VideoCapture(Width, Height);
	}
	else
	{
		return NULL;
	}
}

//构造函数
VideoCapture::VideoCapture(int Width ,int Height)
{
	img_width = Width;
	img_height = Height;
	n_buffers = 0;
	cap_image_size = 0;//to keep the real image size!!
	fd = -1;
	frame_420 = new uint8_t[Width*Height*3/1];
}

//析构函数
VideoCapture::~VideoCapture()
{
	delete [] frame_420;
}

//捕获一帧函数接口
int VideoCapture::Capture_Frame(FRAME_BUF *outBuf)
{
	if(read_frame(outBuf) == 1)
	{
		return 1;
	}
	else
	{
		return 0;
	}

}

//捕获设备初始化接口
void VideoCapture::Capture_Device_init(const char* dev_name)
{
	//初始化设备
	open_device(dev_name);
	init_device(dev_name);
	start_capturing();
	printf( "start_capturing succeed!\n");
}

//
void VideoCapture::Capture_Device_release(void)
{
	stop_capturing( );
	uninit_device( );
	close_device( );
}

//private:
	//函数声明部分
void VideoCapture::errno_exit(const char* s)
{
	printf( "%s error %d, %s\n", s, errno, strerror(errno));
	exit(EXIT_FAILURE);
}

int VideoCapture::xioctl(int fd, int request, void* arg)
{
	int r;
	do
	{
		 r = ioctl(fd, request, arg);
	}while (-1 == r && EINTR == errno);
	return r;
}

void VideoCapture::open_device(const char* dev_name)
{
	struct stat st;
	if (-1 == stat(dev_name, &st))
	{
		printf( "Cannot identify '%s': %d, %s\n", dev_name, errno,strerror(errno));
		exit(EXIT_FAILURE);
	}

	if (!S_ISCHR(st.st_mode))
	{
		printf( "%s is no device\n", dev_name);
		exit(EXIT_FAILURE);
	}
	// 以阻塞方式打开设备
	fd = open(dev_name, O_RDWR, 0);
	if (-1 == fd)
	{
		printf( "Cannot open '%s': %d, %s\n", dev_name, errno, strerror(errno));
		exit(EXIT_FAILURE);
	}
}

void VideoCapture::init_device (const char* dev_name)
{
    struct v4l2_capability cap;
    unsigned int min;
    //查询设备驱动的功能
    //获取摄像头参数
    if (-1 == xioctl(fd, VIDIOC_QUERYCAP, &cap))
    {
        if (EINVAL == errno)
        {
            printf( "%s is no V4L2 device\n", dev_name);
            exit(EXIT_FAILURE);
        } else
        {
            errno_exit("VIDIOC_QUERYCAP");
        }
    }

	{
		printf("Capability Informations:\n");
		printf(" driver: %s\n", cap.driver);
		printf(" card: %s\n", cap.card);
		printf(" bus_info: %s\n", cap.bus_info);
		printf(" version: %08X\n", cap.version);
		printf(" capabilities: %08X\n", cap.capabilities);
	}

    if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))
    {
        printf( "%s is no video capture device\n", dev_name);
        exit(EXIT_FAILURE);
    }

	//获取当前视频设备支持的视频格式
	struct v4l2_fmtdesc fmtdesc;
	memset(&fmtdesc, 0, sizeof(fmtdesc));
	fmtdesc.index = 0;
	fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	int ret = -1;
	while (( ret = ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc)) == 0)
	{
		fmtdesc.index++;
		printf("{ pixelformat = ''%c%c%c%c'', description = ''%s'' }\n",
				(fmtdesc.pixelformat >> 0 ) & 0xFF,
				(fmtdesc.pixelformat >> 8 ) & 0xFF,
				(fmtdesc.pixelformat >> 16) & 0xFF,
				(fmtdesc.pixelformat >> 24) & 0xFF, fmtdesc.description);
	}
	//设置视频设备的视频数据格式，例如设置视频图像数据的长、宽，图像格式(JPEG、YUYV格式)
	struct v4l2_format fmt;
	memset(&fmt, 0, sizeof(fmt));
	fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	fmt.fmt.pix.width = img_width;
	fmt.fmt.pix.height = img_height;
	fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV;//V4L2_PIX_FMT_YUV420;//V4L2_PIX_FMT_YUYV;
   fmt.fmt.pix.field = V4L2_FIELD_INTERLACED;

    {
        printf("-#-#-#-#-#-#-#-#-#-#-#-#-#-\n");
        printf("=====will set fmt to (%d, %d)--", fmt.fmt.pix.width, fmt.fmt.pix.height);
        if (fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_YUYV)
        {
            printf("V4L2_PIX_FMT_YUYV\n");
        }
        else if (fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_YUV420)
        {
            printf("V4L2_PIX_FMT_YUV420\n");
        }
        else if (fmt.fmt.pix.pixelformat == V4L2_PIX_FMT_NV12)
        {
            printf("V4L2_PIX_FMT_NV12\n");
        }
    }

    //设置图像格式
    if (-1 == xioctl(fd, VIDIOC_S_FMT, &fmt))
        errno_exit("VIDIOC_S_FMT");
    {
        printf("=====after set fmt\n");
        printf("    fmt.fmt.pix.width = %d\n", fmt.fmt.pix.width);
        printf("    fmt.fmt.pix.height = %d\n", fmt.fmt.pix.height);
        printf("    fmt.fmt.pix.sizeimage = %d\n", fmt.fmt.pix.sizeimage);
        cap_image_size = fmt.fmt.pix.sizeimage;
        printf("    fmt.fmt.pix.bytesperline = %d\n", fmt.fmt.pix.bytesperline);
        printf("-#-#-#-#-#-#-#-#-#-#-#-#-#-\n");
   }
    cap_image_size = fmt.fmt.pix.sizeimage;

    // 获取视频格式
	if (-1 == xioctl(fd, VIDIOC_G_FMT, &fmt))
		errno_exit("VIDIOC_S_FMT");
	{
		printf("Stream Format Informations:\n");
		printf(" type: %d\n", fmt.type);
		printf(" width: %d\n", fmt.fmt.pix.width);
		printf(" height: %d\n", fmt.fmt.pix.height);
		char fmtstr[8];
		memset(fmtstr, 0, 8);
		memcpy(fmtstr, &fmt.fmt.pix.pixelformat, 4);
		printf(" pixelformat: %s\n", fmtstr);
		printf(" field: %d\n", fmt.fmt.pix.field);
		printf(" bytesperline: %d\n", fmt.fmt.pix.bytesperline);
		printf(" sizeimage: %d\n", fmt.fmt.pix.sizeimage);
		printf(" colorspace: %d\n", fmt.fmt.pix.colorspace);
		printf(" priv: %d\n", fmt.fmt.pix.priv);
		printf(" raw_date: %s\n", fmt.fmt.raw_data);
	}

    min = fmt.fmt.pix.width * 2;
    if (fmt.fmt.pix.bytesperline < min)
        fmt.fmt.pix.bytesperline = min;
    min = fmt.fmt.pix.bytesperline * fmt.fmt.pix.height;
    if (fmt.fmt.pix.sizeimage < min)
        fmt.fmt.pix.sizeimage = min;
    {
    	printf("After Buggy driver paranoia\n");
    	printf("    >>fmt.fmt.pix.sizeimage = %d\n", fmt.fmt.pix.sizeimage);
    	printf("    >>fmt.fmt.pix.bytesperline = %d\n", fmt.fmt.pix.bytesperline);
    	printf("-#-#-#-#-#-#-#-#-#-#-#-#-#-\n");
    }
    init_mmap(dev_name);//初始化
    printf("init_frame420_buffer succeed-0!\n");
}

void VideoCapture::init_mmap(const char* dev_name)
{
	/*请求V4L2驱动分配视频缓冲区(申请V4L2视频驱动分配内存)，V4L2是视频设备的驱动层，
		 位于内核空间，所以通过VIDIOC_REQBUFS控制命令字申请的内存位于内核空间，应用程序
		 不能直接访问，需要通过调用mmap内存映射函数把内核空间内存映射到用户空间后，应用程
		 序通过访问用户空间地址来访问内核空间。*/
	    struct v4l2_requestbuffers req;
	    memset(&req, 0, sizeof(req));
	    req.count = REQ_COUNT;
	    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	    req.memory = V4L2_MEMORY_MMAP;
	    if (-1 == xioctl(fd, VIDIOC_REQBUFS, &req))
	    {
	        if (EINVAL == errno)
	        {
	            fprintf(stderr, "%s does not support memory mapping\n", dev_name);
	            exit(EXIT_FAILURE);
	        }
	        else
	        {
	        	errno_exit("VIDIOC_REQBUFS");
	        }
	    }

	    if (req.count < 2)
	    {
	        fprintf(stderr, "Insufficient buffer memory on %s\n", dev_name);
	        exit(EXIT_FAILURE);
	    }
	    //内存中建立对应空间
	    buffers =(FRAME_BUF*) calloc(req.count, sizeof(*buffers));
	    if (!buffers)
	    {
	        fprintf(stderr, "Out of memory\n");
	        exit(EXIT_FAILURE);
	    }
	    for (n_buffers = 0; n_buffers < req.count; ++n_buffers)
	    {
	    	struct v4l2_buffer buf; //驱动中的一帧
	    	memset(&buf, 0, sizeof(buf));
	        buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	        buf.memory = V4L2_MEMORY_MMAP;
	        buf.index = n_buffers;
	        if (-1 == xioctl(fd, VIDIOC_QUERYBUF, &buf))
	            errno_exit("VIDIOC_QUERYBUF");
	        //通过mmap建立内核空间与用户空间的映射关系
	        buffers[n_buffers].length = buf.length;
	        buffers[n_buffers].start = mmap(NULL , buf.length,
	                PROT_READ | PROT_WRITE ,
	                MAP_SHARED , fd, buf.m.offset);
	        if (MAP_FAILED == buffers[n_buffers].start)
	            errno_exit("mmap");
	    }
}
void VideoCapture::start_capturing(void)
{
	unsigned int i;
	enum v4l2_buf_type type;
	for (i = 0; i < n_buffers; ++i)
	{
		struct v4l2_buffer buf;
		memset(&buf, 0, sizeof(buf));
		buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		buf.memory = V4L2_MEMORY_MMAP;
		buf.index = i;
		//申请到的缓冲进入列队
		if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
		errno_exit("VIDIOC_QBUF");
	}
	// 开始录制
	type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	if (-1 == xioctl(fd, VIDIOC_STREAMON, &type))
		errno_exit("VIDIOC_STREAMON");

}

int VideoCapture::read_frame(FRAME_BUF *outbuf)
{
	struct v4l2_buffer buf;
	memset(&buf,0,sizeof(buf));
	buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	buf.memory = V4L2_MEMORY_MMAP;
	if (-1 == xioctl(fd, VIDIOC_DQBUF, &buf))
	{
		switch (errno)
		{
			case EAGAIN:
			return 0;
			case EIO:
			default:
			errno_exit("VIDIOC_DQBUF");
		}
	}
	assert(buf.index < n_buffers);
//	fprintf(stderr,"image_size = %d,\t IO_METHOD_MMAP buffer.length=%d\r",
//		cap_image_size, buffers[0].length);
	//将获得的帧拷贝到AVPacket中
	change_422_to_420((uint8_t*)buffers[0].start,img_width, img_height);//将捕获的422视频转换为420格式
	outbuf->start = frame_420;
	outbuf->length = (img_width*img_height*3 >> 1);
	if (-1 == xioctl(fd, VIDIOC_QBUF, &buf))
	errno_exit("VIDIOC_QBUF");
	return 1;
}

void VideoCapture::stop_capturing(void)
{
	enum v4l2_buf_type type;
	type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	if (-1 == xioctl(fd, VIDIOC_STREAMOFF, &type))
		errno_exit("VIDIOC_STREAMOFF");
}

void VideoCapture::uninit_device(void)
{
    unsigned int i;
    for (i = 0; i < n_buffers; ++i)
    	if (-1 == munmap(buffers[i].start, buffers[i].length))
    		errno_exit("munmap");
    free(buffers);
}


void VideoCapture::close_device(void)
{
	if (-1 == close(fd))
		errno_exit("close");
	fd = -1;
}
void VideoCapture::change_422_to_420(const uint8_t* frame_422,int width, int height)
{
	uint8_t *y=frame_420;
	uint8_t *u=&frame_420[ width*height];
	uint8_t *v=&frame_420[ width*height+ width*height/4];
	int i=0,j=0,l=0;
	for(j=0;j<height;j++)
		for(i=0;i< width*2;i++,l++)
		{
			if(j%2==0)
			{//even line to sample U-Chriminance
				if(l==1)
				{//sample U-Chriminance
					*u=frame_422[j* width*2+i];
					u++;
				}
				else if(l==3)
				{//abandon V-Chroma
					l=-1;
					continue;
				}
				else
				{
					*y=frame_422[j* width*2+i];
					++y;
				}
			}
			else if(j%2==1)
			{//odd lines to sample  V-Chroma
				if(l==1)
				{
					continue;
				}
				else if(l==3)
				{
					l=-1;
					*v=frame_422[j* width*2+i];
					++v;
				}
				else
				{
					*y=frame_422[j* width*2+i];
					++y;
				}
			}
		}
}

//*/
